/**
 * the client base url (CLIENTURL) must be set up at the moment of script installation for the user account!
 * 
 */
var CLIENTURL = "http://xxx.erecruit.com";

var LABELNAMES = ["contact", "candidate", 
                  "erp"] // this is a special label used to mark all threads that have been processed by processInbox and not labelled with other labels of interest

var PAGESIZE = 100; // size of the page used to process potentially big lists of threads
var TIMELIMIT = 100000; // "age" of thread limit. If last message of some thread is "older" than this limit than the thread is ignored
var BATCHSIZE = 50; // how many emails can be batched in one service call

/**
 * this service end point is called by POST and the payload is a list of email addresses
 * the service matches the emails against the data base and return the JSON object
 * Property "error" may contain error message and in thisd case nothing else returned
 * otherwise the array of objects returned as "categories" property
 * Each object has "name" property that indicates user category name that is supposed to coinside with the lable name
 * The Emails property contains a list of emails that are taken from the service input and are actually belong to the users of the category
 */
var SERVICEURL = CLIENTURL + "/emails/bytype";

/**
 * this method must be called at the moment of script installation for the user account
 * at this moment user would authorize the script
 */
function setup(){
  for (var i = 0; i < LABELNAMES.length; i++){
    GmailApp.createLabel(LABELNAMES[i]);
  }
}

/**
 * this method processes all current inbox threads and label those that have recognizable emails
 * The method is expected to be run periodically by tirgger associated with the user google account (attached to some spreadsheet)
 */
function processInbox(){
  
  var labels = [];
  for (var i = 0; i < LABELNAMES.length; i++){
    labels.push(new Label(LABELNAMES[i]));
  }
  
  // find "recent" unmarked threads and corresponding email addresses
  
  var unmarkedThreads = [];
  var emailsToResolve = [];
    
  var now = (new Date()).getTime();
  
  var pageIndex = 0;
  var count = PAGESIZE;
  while (count == PAGESIZE){
    var threads = GmailApp.getInboxThreads(pageIndex, PAGESIZE);
    pageIndex += PAGESIZE;
    count = threads.length;
    for (var i = 0; i < count; i++){
      var thread = threads[i];
      
      // skip too old threads
      var lastMessageTime = thread.getLastMessageDate().getTime();
      if (now - lastMessageTime > TIMELIMIT) continue;
      
      var threadLabels = thread.getLabels();
      var email = getEmail(thread);
      var labeled = false;
      
      for (var j = 0; j < threadLabels.length; j++){
        var threadLabelName = threadLabels[j].getName();
        for (var k = 0; k < labels.length; k++){
          labeled = labels[k].isLabeled(thread, threadLabelName, email);
          if (labeled) break;
        }
        if (labeled) break;
      }
      
      if (!labeled){
        unmarkedThreads.push(thread);
        if (!found(email, emailsToResolve)){
          emailsToResolve.push(email);
        }
      }  
    }
  }
  
  // try to label unmarked threads
  
  while (emailsToResolve.length > 0){
    // take next batch of email addresses (and remove them from the list)
    // and call the service to resolve the email addresses
    var responseData = fetchEmailsByCategory(emailsBatch);
    
    // if the error on the batch leave the batch unprocessed
    if (!responseData.error){
      // if we got emails back then they are groupped into categories
      for (var i = 0; i < responseData.categories.length; i++){
        
        // for each category try to find corresponding label and label appropriate threads
        var category = responseData.categories[i];
        for (var j = 0; j < labels.length; j++){
          if (labels[j].isSelectedToLabel(category, unmarkedThreads)) break;
        }
      }
      
      // perform labelling of the selected threads
      for (var i = 0; i < labels.length; i++){
        labels[i].addToLabel();
      }
    }
  }
  
  // label the rest of unprocessed as "erp" (eRecruit processed")
  labels[labels.length - 1].addToLabel(); 
};

/**
 * Helper function to bring the categorized emails from the service
 */
function fetchEmailsByCategory(emailsBatch){
  // call the service to resolve the email addresses
  var response = UrlFetchApp.fetch(SERVICEURL, {
    "method": "post",
    "payload": JSON.stringify(emailsBatch)
  });

  return JSON.parse(response.getContentText());
}

/**
 * Helper object to facilitate processing of the label
 */
function Label(labelName){
  
  // get label for this label name
  this.labelName = labelName;
  this.label = GmailApp.getUserLabelByName(labelName);
  
  // get all unique email addresses associated currently with this label
  this.emails = [];
  if (this.label){
    var count = PAGESIZE;
    var pageIndex = 0;
    while (count == PAGESIZE){
      var threads = this.label.getThreads(pageIndex, PAGESIZE);
      pageIndex += PAGESIZE;
      count = threads.length;
      for (var i = 0; i < count; i++){
        var email = getEmail(threads[i]);
        if (!found(email, this.emails)){
          this.emails.push(email);
        }
      }
    }
  }
  
  // initialize thread list associated with the label
  this.threads = [];
}

Label.prototype.isLabeled = function(thread, threadLabelName, email){
  if (!found(this.labelName, threadLabelName)){
    if (found(email, this.emails)){
      this.threads.push(thread);
      return true;
    }
    return false;
  }
  return true;
}

Label.prototype.isSelectedToLabel = function(category, unmarkedThreads){
  if (category.name !== this.labelName) return false;
  this.emails = category.emails;
  for (var i = 0; i < unmarkedThreads; i++){
    var thread = unmarkedThreads[i];
    if (found(getEmail(thread), this.emails)){
      this.threads.push(thread);
      unmarkedThreads.splice(i,1);
    }
  }
  
  return true;
}

Label.prototype.addToLabel = function(){
  if (this.threads.length > 0){
    this.label.addToThreads(this.threads);
    this.threads = null;
  }
  this.emails = null;
}


/**
 * Utility methods
 */

function found(key, keys){
  for (var i = 0; i < keys.length; i++){
    if (key === keys[i]) return true;
  }
  return false
}

function getEmail(thread){
  var email = thread.getMessages()[0].getFrom();
  email = email.substring(email.indexOf("<") + 1);
  return email.substring(0, email.length - 1).toLowerCase();
}